package com.ysw.processor;

import com.google.common.collect.Maps;
import com.ysw.ConfigContext;
import com.ysw.base.ProcessorEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * @ClassName SpringValueProcessor
 * @Description 这个类要在beanDefinition实例化所有对象之前进行初始化
 * 因为要进行spring value 的管理所有populateBean之前进行处理
 * 所以不能使用springboo自动装配或者是@Compnent注解进行注入，类似与beanPostProcessor，要提前进行初始化
 * Spring value Processor 这个需要被spring processors 管理，因为要根据beanPostProcessor 来收集 属性类的信息
 * @Author ysw
 * @Date 2021/10/29 0:21
 */
public class SpringValueProcessor implements ConfigProcessor, BeanPostProcessor, EnvironmentAware {

    private final Logger logger = LoggerFactory.getLogger(SpringValueProcessor.class);

    @Override
    public Enum getProcessorType() {
        return ProcessorEnum.SPRING_PROCESSOR;
    }

    /**
     * key @value 加在类的属性上
     * <p>
     * value map[field, bean]
     */
    private final Map<String, Map<Field, Object>> springValueAtFieldBeans = Maps.newLinkedHashMap();

    /**
     * key @value 加在类的set方法上
     * <p>
     * value map[method, bean]
     */
    private final Map<String, Map<Method, Object>> springValueAtMethodBeans = Maps.newLinkedHashMap();

    /**
     * 模拟数据来源，config.properties,这种数据来源可以是其他的文件形式
     * 看自己业务组装,
     */
    @Override
    public void initConfig(ConfigContext configContext) {
        logger.info("spring value processor init start");
        try {
            Properties properties = PropertiesLoaderUtils.loadAllProperties("config.properties");
            // 不同方式的配置文件可以设置优先级
            configContext.mergePropertiesToEnvironment(properties);
        } catch (IOException e) {
            logger.error("get config error :{}", e);
        }
    }

    /**
     * 产生事件变更后处理
     * 这里测试采用http请求的方式进行处理
     *
     * @param configContext
     * @param key
     * @param value
     */
    @Override
    public void listenConfig(ConfigContext configContext, String key, String value) {
        Properties properties = new Properties();
        properties.put(key, value);
        // 更新environment 中的值，但是仅仅更新这个是不会生效的，因为populate Bean 已经发生了，不可能再重新去实例化bean
        // 之前的缓存现在用上了 springValueAtFieldBeans springValueAtMethodBeans，之前一直维护了@Value 属性的引用
        configContext.mergePropertiesToEnvironment(properties);
        properties.stringPropertyNames().forEach(name -> {
            // 属性
            Map<Field, Object> fieldMap = springValueAtFieldBeans.get(name);
            if (!CollectionUtils.isEmpty(fieldMap)) {
                for (Field f : fieldMap.keySet()) {
                    f.setAccessible(true);
                    try {
                        //  值相等，直接忽略
                        if (f.get(fieldMap.get(f)).equals(getProperty(properties, name))) {
                            continue;
                        }
                        //判断类型
                        if (f.getType().equals(String.class)) {
                            f.set(fieldMap.get(f), getProperty(properties, name));
                        } else if (f.getType().equals(Long.class) || f.getType().equals(long.class)) {
                            f.set(fieldMap.get(f), Long.valueOf(getProperty(properties, name)));
                        } else if (f.getType().equals(Double.class) || f.getType().equals(double.class)) {
                            f.set(fieldMap.get(f), Double.valueOf(getProperty(properties, name)));
                        } else if (f.getType().equals(Integer.class) || f.getType().equals(int.class)) {
                            f.set(fieldMap.get(f), Integer.valueOf(getProperty(properties, name)));
                        } else if (f.getType().equals(Boolean.class) || f.getType().equals(boolean.class)) {
                            f.set(fieldMap.get(f), Boolean.valueOf(getProperty(properties, name)));
                        }
                    } catch (Exception e) {
                        logger.error("[config] 监听到spring value key变化刷新异常, {}", e);
                    }
                }
            }

            // 方法
            Map<Method, Object> methodMap = springValueAtMethodBeans.get(name);
            if (!CollectionUtils.isEmpty(methodMap)) {
                for (Method m : methodMap.keySet()) {
                    m.setAccessible(true);
                    try {
                        if (m.getParameterCount() > 0) {
                            Class<?>[] parameterTypes = m.getParameterTypes();
                            // 取第一个
                            if (parameterTypes[0].equals(String.class)) {
                                m.invoke(methodMap.get(m), getProperty(properties, name));
                            } else if (parameterTypes[0].equals(Long.class) || parameterTypes[0].equals(long.class)) {
                                m.invoke(methodMap.get(m), Long.valueOf(getProperty(properties, name)));
                            } else if (parameterTypes[0].equals(Double.class) || parameterTypes[0].equals(double.class)) {
                                m.invoke(methodMap.get(m), Double.valueOf(getProperty(properties, name)));
                            } else if (parameterTypes[0].equals(Integer.class) || parameterTypes[0].equals(int.class)) {
                                m.invoke(methodMap.get(m), Integer.valueOf(getProperty(properties, name)));
                            } else if (parameterTypes[0].equals(Boolean.class) || parameterTypes[0].equals(boolean.class)) {
                                m.invoke(methodMap.get(m), Boolean.valueOf(getProperty(properties, name)));
                            }
                        }
                    } catch (Exception e) {
                        logger.error("[config] 监听到etcd key变化刷新异常, {}", e);
                    }
                }
            }
        });

    }

    /**
     * 这里是实现了beanPostProcessor 方式对bean 的信息进行收集
     *
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        saveCacheFieldBeans(bean);
        saveCacheMethodBeans(bean);
        return bean;
    }

    /**
     * 存入spring cache bean
     * 处理 带有@Value的field
     *
     * @param bean
     */
    private void saveCacheFieldBeans(Object bean) {
        Class<?> beanClass = bean.getClass();
        List<Field> fields = findAllField(beanClass);
        fields.forEach(field -> {
            Value value = field.getAnnotation(Value.class);
            if (value != null) {
                String name = value.value();
                name = name.replace("${", "").replace("}", "");
                if (springValueAtFieldBeans.containsKey(name)) {
                    springValueAtFieldBeans.get(name).put(field, bean);
                } else {
                    Map<Field, Object> fieldMap = Maps.newLinkedHashMap();
                    fieldMap.put(field, bean);
                    springValueAtFieldBeans.put(name, fieldMap);
                }
            }
        });
    }

    /**
     * 处理带有@value 的method 方法
     *
     * @param bean
     */
    private void saveCacheMethodBeans(Object bean) {
        // 方法
        List<Method> methods = findAllMethod(bean.getClass());
        for (Method method : methods) {
            Value value = method.getAnnotation(Value.class);
            if (value != null) {
                String name = value.value();
                name = name.replace("${", "").replace("}", "");
                name = name.split(":")[0];
                if (springValueAtMethodBeans.containsKey(name)) {
                    springValueAtMethodBeans.get(name).put(method, bean);
                } else {
                    Map<Method, Object> methodMap = Maps.newLinkedHashMap();
                    methodMap.put(method, bean);
                    springValueAtMethodBeans.put(name, methodMap);
                }
            }
        }
    }

    /**
     * 获取反射field
     *
     * @param clazz
     * @return
     */
    private List<Field> findAllField(Class clazz) {
        final List<Field> res = new LinkedList<>();
        ReflectionUtils.doWithFields(clazz, field -> res.add(field));
        return res;
    }

    /**
     * 获取反射method
     *
     * @param clazz
     * @return
     */
    private List<Method> findAllMethod(Class clazz) {
        final List<Method> res = new LinkedList<Method>();
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getName().startsWith("set")) {
                res.add(method);
            }
        }
        return res;
    }

    /**
     * 根据key获取属性值
     *
     * @param p
     * @param key
     * @return
     */
    protected String getProperty(Properties p, String key) {
        if (p == null) {
            throw new IllegalStateException(String.format("properties is null, current key is [%s]", key));
        }
        String value = p.getProperty(key);
        if (value == null) {
            throw new IllegalStateException(String.format("key [%s] not found", key));
        }
        return value;
    }

    @Override
    public void setEnvironment(Environment environment) {

    }
}